using System;
using System.Reflection.Emit;
using Pine.Core;

namespace Pine.CodeGen
{
	public static class CodeUtilities
	{
		public static void MakeGetListValue(ILGenerator generator, LocalBuilder list, double key, LocalBuilder lb) {
			generator.Emit(OpCodes.Ldloc, list);
			generator.Emit(OpCodes.Ldc_R8, key);
			generator.Emit(OpCodes.Call, typeof(ValueList).GetMethod("GetArrayElement", new Type[] { typeof(double) }));
			generator.Emit(OpCodes.Stloc, lb);
		}
		
		public static void MakeGetListValue(ILGenerator generator, LocalBuilder list, string key, LocalBuilder lb) {
			generator.Emit(OpCodes.Ldstr, key);
			generator.Emit(OpCodes.Newobj, typeof(StringValue).GetConstructor(new Type[] { typeof(string) }));
			LocalBuilder lv = generator.DeclareLocal(typeof(Number));
			generator.Emit(OpCodes.Stloc, lv);
			generator.Emit(OpCodes.Ldloc, list);
			generator.Emit(OpCodes.Ldloc, lv);
			generator.Emit(OpCodes.Call, typeof(ValueList).GetMethod("GetArrayElement", new Type[] { typeof(Value) }));
			generator.Emit(OpCodes.Stloc, lb);
		}
		
		public static void MakeGetListValue(ILGenerator generator, LocalBuilder list, object key, LocalBuilder lb) {
			if (key is double)
				MakeGetListValue(generator, list, (double) key, lb);
			else if (key is string)
				MakeGetListValue(generator, list, (string) key, lb);
			else
				throw new Exception("Invalid list value type: " + key.GetType());
		}
		
		public static void MakeSetListValue(ILGenerator generator, LocalBuilder list, double key, LocalBuilder val) {
			generator.Emit(OpCodes.Ldc_R8, key);
			generator.Emit(OpCodes.Newobj, typeof(Number).GetConstructor(new Type[] { typeof(double) }));
			LocalBuilder lv = generator.DeclareLocal(typeof(Number));
			generator.Emit(OpCodes.Stloc, lv);
			generator.Emit(OpCodes.Ldloc, list);
			generator.Emit(OpCodes.Ldloc, lv);
			generator.Emit(OpCodes.Ldloc, val);
			generator.Emit(OpCodes.Call, typeof(ValueList).GetMethod("SetArrayElement"));
		}
		
		public static void MakeSetListValue(ILGenerator generator, LocalBuilder list, string key, LocalBuilder val) {
			generator.Emit(OpCodes.Ldstr, key);
			generator.Emit(OpCodes.Newobj, typeof(StringValue).GetConstructor(new Type[] { typeof(string) }));
			LocalBuilder lv = generator.DeclareLocal(typeof(Number));
			generator.Emit(OpCodes.Stloc, lv);
			generator.Emit(OpCodes.Ldloc, list);
			generator.Emit(OpCodes.Ldloc, lv);
			generator.Emit(OpCodes.Ldloc, val);
			generator.Emit(OpCodes.Call, typeof(ValueList).GetMethod("SetArrayElement"));
		}
		
		public static void MakeSetListValue(ILGenerator generator, LocalBuilder list, object key, LocalBuilder val) {
			if (key is double)
				MakeSetListValue(generator, list, (double) key, val);
			else if (key is string)
				MakeSetListValue(generator, list, (string) key, val);
			else
				throw new Exception("Invalid list value type: " + key.GetType());
		}
		
		public static void MakeClearList(ILGenerator generator, LocalBuilder list) {
			generator.Emit(OpCodes.Ldloc, list);
			generator.Emit(OpCodes.Call, typeof(ValueList).GetMethod("Clear"));	
		}
		
		public static Context MakeChildScope(Context ctx) {
			LocalBuilder newScope = ctx.il.DeclareLocal(typeof(IScope));
			ctx.il.Emit(OpCodes.Ldloc, ctx.scope);
			ctx.il.Emit(OpCodes.Newobj, typeof(BlockScope).GetConstructor(new Type[] { typeof(IScope) }));
			ctx.il.Emit(OpCodes.Stloc, newScope);
			
			Context nctx = new Context(ctx);
			nctx.scope = newScope;
			
			return nctx;
		}
		
		public static Context MakeWhileContext(Context ctx, string name, Label continueLabel, Label breakLabel) {
			Context nctx = MakeChildScope(ctx);
			
			nctx.loopNames.Push(name);
			nctx.continueLabels.Push(continueLabel);
			nctx.breakLabels.Push(breakLabel);
			
			return nctx;
		}
		
		public static void MakeScopeDeclare(ILGenerator ilGenerator, LocalBuilder scope, bool isConst, LocalBuilder newValue, string variable) {
			ilGenerator.Emit(OpCodes.Ldloc, scope);
			if (isConst)
				ilGenerator.Emit(OpCodes.Ldc_I4_1);
			else
				ilGenerator.Emit(OpCodes.Ldc_I4_0);
			ilGenerator.Emit(OpCodes.Ldstr, variable);
			ilGenerator.Emit(OpCodes.Ldloc, newValue);
			ilGenerator.Emit(OpCodes.Callvirt, typeof(IScope).GetMethod("Declare"));
		}
		
		public static void MakeScopeRead(Context ctx, string variable) {
			ctx.il.Emit(OpCodes.Ldloc, ctx.scope);
			ctx.il.Emit(OpCodes.Ldstr, variable);
			ctx.il.Emit(OpCodes.Callvirt, typeof(IScope).GetMethod("GetItem")); 
		}
		
		public static void MakeScopeWrite(Context ctx, string variable, LocalBuilder newValue) {
			ctx.il.Emit(OpCodes.Ldloc, ctx.scope);
			ctx.il.Emit(OpCodes.Ldstr, variable);
			ctx.il.Emit(OpCodes.Ldloc, newValue);
			ctx.il.Emit(OpCodes.Callvirt, typeof(IScope).GetMethod("SetItem"));
		}
	}
}

